#ifdef _WIN32

#include "Console.h"
#include "Exceptions.win32.h"
#include "BufferedStream.h"
#include "FileStream.h"
#include "StreamReader.h"
#include <Windows.h>
#include <boost/scoped_array.hpp>
#include <vector>

using namespace std;

namespace lhcutils
{

ConsoleClass Console();

ConsoleClass::ConsoleClass() : m_stdoutIsConsole(false), m_stdinIsConsole(false)
{
	m_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

	if(m_stdoutHandle == INVALID_HANDLE_VALUE)
	{
		THROW_WIN32IO(ustr("Failed to initialize console."));
	}

	if(m_stdoutHandle != NULL) // stdout handle could easily be null if we're in a GUI program
	{
		// Check if it's a console or something else
		// If console, use WriteConsoleW when writing, otherwise use WriteFile
		m_stdoutFileType = GetFileType(m_stdoutHandle);
		if(m_stdoutFileType == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR)
		{
			THROW_WIN32IO(ustr("Failed to initialize console."));
		}

		if(m_stdoutFileType == FILE_TYPE_CHAR)
		{
			DWORD consoleMode;
			BOOL success = GetConsoleMode(m_stdoutHandle, &consoleMode);
			if(success != 0)
			{
				m_stdoutIsConsole = true;
			}
		}
	}

	m_stdinHandle = GetStdHandle(STD_INPUT_HANDLE);

	if(m_stdinHandle == INVALID_HANDLE_VALUE)
	{
		THROW_WIN32IO(ustr("Failed to initialize console."));
	}

	if(m_stdinHandle != NULL)
	{
		m_stdinFileType = GetFileType(m_stdinHandle);
		if(m_stdinFileType == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR)
		{
			THROW_WIN32IO(ustr("Failed to initialize console."));
		}

		if(m_stdinFileType == FILE_TYPE_CHAR)
		{
			DWORD consoleMode;
			BOOL success = GetConsoleMode(m_stdinHandle, &consoleMode);
			if(success != 0)
			{
				m_stdinIsConsole = true;
			}
		}
		else
		{
			boost::shared_ptr<FileStream> stdinStream(new FileStream(m_stdinHandle));
			boost::shared_ptr<BufferedStream> bufferedStdinStream(new BufferedStream(stdinStream));
			m_stdinRedirectedStream.reset(new StreamReader(bufferedStdinStream));
		}
	}
}

ConsoleClass::~ConsoleClass()
{
	;
}

void ConsoleClass::Write(const UnicodeString& str)
{
	if(m_stdoutHandle == NULL)
	{
		return;
	}
	
	if(m_stdoutIsConsole)
	{
		// stdout is a console, use WriteConsoleW with UTF-16
		WriteTrueConsole(str);
	}
	else
	{
		// stdout is being redirected to a file or piped somewhere, use WriteFile with UTF-8
		WriteStdoutRedirected(str);
	}
}

void ConsoleClass::WriteTrueConsole(const UnicodeString& str)
{
	boost::shared_ptr<const CountedArray<uint8_t> > winStr = str.ToEncoding(encodings::Utf16);

	DWORD numCharsWrittenInLastWrite;
	DWORD numCharsWritten = 0;

	while(numCharsWritten < str.GetCodeUnitLength())
	{
		DWORD numCharsToWrite = str.GetCodeUnitLength() - numCharsWritten;
		DWORD charOffset = numCharsToWrite - str.GetCodeUnitLength();
		DWORD byteOffset = charOffset * 2;

		BOOL success = WriteConsoleW(m_stdoutHandle, winStr->Ptr + byteOffset, numCharsToWrite, &numCharsWrittenInLastWrite, NULL);
		if(success == 0)
		{
			if(GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
			{
				THROW_WIN32IO(ustr("Tried to write too many characters to the console."));
			}
			else
			{
				THROW_WIN32IO(ustr("Error writing to console."));
			}
		}

		numCharsWritten += numCharsWrittenInLastWrite;
	}
}

void ConsoleClass::WriteStdoutRedirected(const UnicodeString& str)
{
	boost::shared_ptr<const CountedArray<uint8_t> > utf8 = str.ToEncoding(encodings::Utf8);

	DWORD numBytesWrittenInLastWrite;
	DWORD numBytesWritten = 0;
	while(numBytesWritten < utf8->Count)
	{
		DWORD numBytesToWrite = utf8->Count - numBytesWritten;
		DWORD offset = numBytesToWrite - utf8->Count;
		BOOL success = WriteFile(m_stdoutHandle, utf8->Ptr + offset, numBytesToWrite, &numBytesWrittenInLastWrite, NULL);
		if(success == 0)
		{
			THROW_WIN32IO(ustr("Error writing to console."));
		}
		numBytesWritten += numBytesWrittenInLastWrite;
	}
}

StringPtr ConsoleClass::ReadLine()
{
	if(m_stdinHandle == NULL)
	{
		return StringPtr();
	}

	if(m_stdinIsConsole)
	{
		return ReadLineTrueConsole();
	}
	else
	{
		return ReadLineStdinRedirected();
	}
}

StringPtr ConsoleClass::ReadLineTrueConsole()
{
	vector<wchar_t> line;
	
	DWORD numCharsRead;
	const int bufferSize = 1024;
	boost::scoped_array<wchar_t> buffer(new wchar_t[bufferSize]);

	while(true)
	{
		BOOL success = ReadConsoleW(m_stdinHandle, buffer.get(), bufferSize, &numCharsRead, NULL);
		if(success == 0)
		{
			if(GetLastError() == ERROR_HANDLE_EOF)
			{
				if(line.size() == 0)
				{
					return StringPtr();
				}
				else
				{
					return StringPtr(new UnicodeString(&(line[0]), line.size() * 2, encodings::Utf16));
				}
			}
			else
			{
				THROW_WIN32IO(ustr("Error reading from console."));
			}
		}

		if(numCharsRead == 0)
		{
			if(line.size() == 0)
			{
				return StringPtr();
			}
			else
			{
				return StringPtr(new UnicodeString(&(line[0]), line.size() * 2, encodings::Utf16));
			}
		}

		for(int i = 0; i < numCharsRead; i++)
		{
			if(buffer[i] == L'\n' || buffer[i] == L'\r')
			{
				return StringPtr(new UnicodeString(&(line[0]), line.size() * 2, encodings::Utf16));
			}
			else
			{
				line.push_back(buffer[i]);
			}
		}
	}
}

StringPtr ConsoleClass::ReadLineStdinRedirected()
{
	return m_stdinRedirectedStream->ReadLine();
}

#endif

/*
 Copyright 2011 Greg Najda

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
